﻿//  Copyright (c) 2010 Ray Liang (http://www.dotnetage.com)
//  Dual licensed under the MIT and GPL licenses:
//  http://www.opensource.org/licenses/mit-license.php
//  http://www.gnu.org/licenses/gpl.html

using System;
using System.Web;
using CookComputing.XmlRpc;
using System.Net;
using System.Linq;
using DNA.Mvc.Management;

namespace DNA.Mvc.Publishing.Services
{
    public class PingbackService : XmlRpcService, IPingbackService
    {
        IPubContext pubContext;

        public PingbackService(IPubContext context)
        {
            pubContext = context;
        }

        /// <summary>
        /// Notifies the server that a link has been added to sourceURI, pointing to targetURI.
        /// </summary>
        /// <param name="sourceURI">The absolute URI of the post on the source page containing the link to the target site.</param>
        /// <param name="targetURI">The absolute URI of the target of the link, as given on the source page.</param>
        /// <returns>A string, as described below.
        ///If an error condition occurs, then the appropriate fault code from the following list should be used. Clients can quickly determine the kind of error from bits 5-8. 0×001x fault codes are used for problems with the source URI, 0×002x codes are for problems with the target URI, and 0×003x codes are used when the URIs are fine but the pingback cannot be acknowledged for some other reaon.
        ///0
        ///A generic fault code. Servers MAY use this error code instead of any of the others if they do not have a way of determining the correct fault code.
        ///0×0010 (16)
        ///The source URI does not exist.
        ///0×0011 (17)
        ///The source URI does not contain a link to the target URI, and so cannot be used as a source.
        ///0×0020 (32)
        ///The specified target URI does not exist. This MUST only be used when the target definitely does not exist, rather than when the target may exist but is not recognised. See the next error.
        ///0×0021 (33)
        ///The specified target URI cannot be used as a target. It either doesn't exist, or it is not a pingback-enabled resource. For example, on a blog, typically only permalinks are pingback-enabled, and trying to pingback the home page, or a set of posts, will fail with this error.
        ///0×0030 (48)
        ///The pingback has already been registered.
        ///0×0031 (49)
        ///Access denied.
        ///0×0032 (50)
        ///The server could not communicate with an upstream server, or received an error from an upstream server, and therefore could not complete the request. This is similar to HTTP's 402 Bad Gateway error. This error SHOULD be used by pingback proxies when propagating errors.
        ///In addition, [FaultCodes] defines some standard fault codes that servers MAY use to report higher level errors.
        ///</returns>
        [XmlRpcMethod("pingback.ping")]
        public string Ping(string sourceURI, string targetURI)
        {
            try
            {
                Uri srcUrl = null;
                Uri.TryCreate(sourceURI, UriKind.Absolute, out srcUrl);
                if (srcUrl == null) throw new XmlRpcFaultException(16, "The source URI does not exist.");

                Uri targetUrl = null;
                Uri.TryCreate(targetURI, UriKind.Absolute, out targetUrl);
                var httpContext = HttpContext.Current;
                var ip = httpContext.Request.UserHostAddress;

                if (targetUrl == null) throw new XmlRpcFaultException(1, "Invalid targetUri parameter.");

                if (!httpContext.Request.Url.Host.Equals(srcUrl.Host, StringComparison.OrdinalIgnoreCase))
                {
                    if (!HasLinkInSourceUri(srcUrl, targetUrl))
                        throw new XmlRpcFaultException(17, "The source URI does not contain a link to the target URI, and so cannot be used as a source.");
                }

                if (!httpContext.Request.IsLocal)
                {
                    var url = srcUrl.ToString().ToLower();

                    //if (pubContext.Spams.Count(s => s.IP.Equals(ip) || url.StartsWith(s.Url)) > 0)
                    if (Spammers.IsSpam(new HttpContextWrapper(httpContext)))
                        return "Your are spammer all request will be block!";
                }

                var article = pubContext.Articles.Find(targetUrl);

                if (article == null)
                    throw new XmlRpcFaultException(32, "The specified target URI does not exist");

                if ((!article.AllowComments) || (!article.AllowPingback))
                    throw new XmlRpcFaultException(33, "The specified target URI cannot be used as a target");

                var comments = article.Comments;

                if (comments.Count > 0)
                    if (comments.FirstOrDefault(c => c.WebSite.Equals(sourceURI.ToString(), StringComparison.OrdinalIgnoreCase)) != null)
                        throw new XmlRpcFaultException(48, "The pingback has already been registered.");

                var referUrl = httpContext.Request.UrlReferrer;
                var comment = new Comment()
                {
                    Title = "Ping back from:" + srcUrl.ToString(),
                    IP = ip,
                    Posted = DateTime.UtcNow,
                    UserName = ip,
                    WebSite = srcUrl.ToString(),
                    IsPingback = true,
                    IsTrackback = false,
                    UrlReferrer = referUrl != null ? referUrl.ToString() : ""
                };
                pubContext.Comments.Create(comment);
                pubContext.SaveChanges();

                new NewCommentEvent(comment).Raise(new HttpContextWrapper(HttpContext.Current));
            }
            catch
            {
                throw new XmlRpcFaultException(1, "Invalid sourceUri parameter.");
            }

            return "Your ping request has been received successfully.";
        }

        private bool HasLinkInSourceUri(Uri source, Uri mustHaveThisLink)
        {
            try
            {
                using (var client = new WebClient())
                {
                    client.Credentials = CredentialCache.DefaultNetworkCredentials;
                    var html = client.DownloadString(source);
                    return html.ToUpperInvariant().Contains(mustHaveThisLink.ToString().ToUpperInvariant());
                }
            }
            catch
            {
                return false;
            }
        }
    }
}